package com.htao.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.htao.train.business.domain.*;
import com.htao.train.business.enums.RedisKeyPreEnum;
import com.htao.train.business.mapper.cust.SkTokenMapperCust;
import com.htao.train.common.resp.PageResp;
import com.htao.train.common.util.SnowUtil;
import com.htao.train.business.mapper.SkTokenMapper;
import com.htao.train.business.req.SkTokenQueryReq;
import com.htao.train.business.req.SkTokenSaveReq;
import com.htao.train.business.resp.SkTokenQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SkTokenService {

    private static final Logger LOG = LoggerFactory.getLogger(SkTokenService.class);

    @Resource
    private SkTokenMapper skTokenMapper;
    @Resource
    private  DailyTrainSeatService dailyTrainSeatService;
    @Resource
    private  DailyTrainStationService dailyTrainStationService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Value("${stress.test}")
    private String startTest;
    private static final String TT= "true";

    @Transactional(rollbackFor = Exception.class)
    public void genDaily(Date date, String trainCode) {
        LOG.info("生成日期【{}】车次【{}】的令牌开始", DateUtil.formatDate(date), trainCode);

        // 删除某日某令牌的车站信息
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.createCriteria()
                .andDateEqualTo(date)
                .andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(skTokenExample);

        // 查出某令牌的所有的车站信息

        DateTime now = DateTime.now();
        SkToken skToken = new SkToken();
        skToken.setId(SnowUtil.getSnowflakeNextId());
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);
        skToken.setDate(date);
        //令牌的数目需要根据车座位和车站数目而定
        //首先查当前日期和当前车次的座位数
        int seatNum = dailyTrainSeatService.countSeat(date, trainCode, null);

        //查当前日期当前车次的车站数目
        int stationNum = dailyTrainStationService.countStation(date, trainCode);
        int tokenNum = 0;
        if(seatNum!=-1&&stationNum!=-1){
            tokenNum = seatNum*stationNum*2;
        }
        skToken.setCount(tokenNum);
        skTokenMapper.insert(skToken);
        LOG.info("生成日期【{}】车次【{}】的令牌结束", DateUtil.formatDate(date), trainCode);
    }

    public void save(SkTokenSaveReq req) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
        if (ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(SnowUtil.getSnowflakeNextId());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        } else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<SkToken> skTokenList = skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);

        PageResp<SkTokenQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }

    public boolean validSkToken(Date date, String trainCode,Long memberId) {
        if(!TT.equals(startTest)){
            //令牌锁
            String lockTokenKey = RedisKeyPreEnum.SK_TOKEN+DateUtil.formatDate(date)+trainCode+memberId;
            Boolean aBooleanToken = stringRedisTemplate.opsForValue().setIfAbsent(lockTokenKey, lockTokenKey, 5, TimeUnit.SECONDS);
            if(BooleanUtil.isTrue(aBooleanToken)){
                LOG.info("恭喜，抢到令牌锁了！lockKey：{}", lockTokenKey);
            }else {
                LOG.info("没抢到令牌锁");
                return false;
            }
        }

        //操作缓存，改为5个请求向sql同步一次
        String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;
        Object skTokenCount = stringRedisTemplate.opsForValue().get(skTokenCountKey);
        if (skTokenCount != null) {
            LOG.info("缓存中有该车次令牌大闸的key：{}", skTokenCountKey);
            Long count = stringRedisTemplate.opsForValue().decrement(skTokenCountKey, 1);
            if (count < 0L) {
                LOG.error("获取令牌失败：{}", skTokenCountKey);
                return false;
            } else {
                LOG.info("获取令牌后，令牌余数：{}", count);
                stringRedisTemplate.expire(skTokenCountKey, 60, TimeUnit.SECONDS);
                // 每获取5个令牌更新一次数据库
                if (count % 5 == 0) {
                    SkToken skToken = new SkToken();
                    skToken.setCount(count.intValue());
                    SkTokenExample skTokenExample = new SkTokenExample();
                    skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
                    skTokenMapper.updateByExampleSelective(skToken,skTokenExample);
                }
                return true;
            }
        } else {
            LOG.info("缓存中没有该车次令牌大闸的key：{}", skTokenCountKey);
            // 检查是否有令牌
            SkTokenExample skTokenExample = new SkTokenExample();
            skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> tokenCountList = skTokenMapper.selectByExample(skTokenExample);
            if (CollUtil.isEmpty(tokenCountList)) {
                LOG.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
                return false;
            }
            SkToken skToken = tokenCountList.get(0);
            if (skToken.getCount() <= 0) {
                LOG.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);
                return false;
            }
            // 令牌还有余量
            // 令牌余数-1
            Integer count = skToken.getCount() - 1;
            skToken.setCount(count);
            LOG.info("将该车次令牌大闸放入缓存中，key: {}， count: {}", skTokenCountKey, count);
            // 不需要更新数据库，只要放缓存即可
            stringRedisTemplate.opsForValue().set(skTokenCountKey, String.valueOf(count), 60, TimeUnit.SECONDS);
            return true;
        }
    }
}
